package logger.sd.server;

import java.io.IOException;
import java.net.BindException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;

import logger.sd.server.service.TokenSender;
import logger.sd.server.service.TokenSenderService;

/**
 * LogServer Base do servidor
 * 
 * @author Wendell
 */
public abstract class AbstractLoggingServer extends Observable implements
		LoggingServer {

	private boolean token;
	private boolean running;
	private Long id;
	private static Integer changes = 0;

	protected LogWriter logger;
	protected ServerSocket listener;
	protected Socket client;
	protected List<Observer> buffer;

	protected ServerConfiguration config;

	/**
	 * Construtor...
	 * 
	 * @param config
	 */
	public AbstractLoggingServer(ServerConfiguration config) {
		this.config = config;
		token = config.isFirst();
		buffer = new ArrayList<Observer>();
		id = (long) (System.currentTimeMillis());
		logger = LogWriter.getInstance(AbstractLoggingServer.class, config);
		try {
			listener = new ServerSocket(config.getPort());
			logger.info("Servidor iniciado em " + InetAddress.getLocalHost()
					+ ":" + listener.getLocalPort());
		} catch (BindException e) {
			logger.error("\nServidor ja esta em execucao em " + config
					+ "\nTente finalizar o processo javaws (no windows) "
					+ "ou java (no linux)");
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	/**
	 * Apresentacao: Imprime texto informativo no console, informando qual a
	 * estrategia do servidor
	 */
	protected abstract void apresenta();

	/**
	 * Tratamento de todas as requisicoes feitas pelos clientes Design Pattern
	 * Strategy
	 * 
	 * @param novaConexao
	 *            A nova conexao que sera atendida
	 * @throws IOException
	 */
	protected abstract void trataRequisicao(Connection novaConexao)
			throws IOException;

	/**
	 * Cria uma nova conexao
	 * 
	 * @return Nova Conexao com o cliente
	 */
	protected Connection criarConexao() {
		return new Connection(client, this);
	}

	/**
	 * Execucao do servidor Cria o Socket na porta padrao Executa servidor.
	 * Trata a requisisicao de conexao
	 * 
	 * @exception BindException
	 * @exception IOException
	 */
	public void start() {
		try {
			// Nao deve fazer schedule agora... apenas enviar o token
			TokenSender tokenSender = new TokenSenderService(this);
			tokenSender.sendToken();

			this.running = true;
			while (running && listener != null) { // Servidor rodando
				client = listener.accept();
				Connection novaConexao = criarConexao();
				trataRequisicao(novaConexao); // Strategy
			}
			if (listener != null) {
				listener.close();
			}
		} catch (IOException e) {
			logger.error(e.toString());
		}
	}

	/**
	 * Stop server
	 */
	public void stop() {
		if (running) {
			running = false;
			logger.debug("Servidor finalizado.");
		}
	}

	/**
	 * Verifica se o servidor esta em execucao
	 * 
	 * @return running
	 */
	public boolean isRunning() {
		return running;
	}

	/**
	 * @return the token
	 */
	public boolean hasToken() {
		return token;
	}

	/**
	 * 
	 */
	public void passarToken() {
		this.token = false;
	}

	/**
	 * Adiciona um observer
	 * @param o
	 */
	@Override
	public void addListener(Observer o) {
		this.buffer.add(o);
		addObserver(o);
	}

	/**
	 * Tratamento da chegada do token
	 */
	public void tokenArrive() {
		this.token = true;
		setChanged();
		notifyObservers(System.currentTimeMillis());
	}

	/**
	 * Adiciona uma mudanca a ser replicada  posteriormente
	 */
	public void addChange() {
		changes++;
	}

	/**
	 * Obtem o nuemro de mudancas nao replicadas
	 */
	public Integer getChanges() {
		return changes;
	}

	/**
	 * Zera o umero de mudancas nao replicadas.
	 * Deve ser chamado apos a replicacao bem sucedida
	 */
	public void clearChanges() {
		changes = 0;
	}

	/**
	 * Obtem o ID do servidor.
	 * @deprecated Nao eh necessario o id
	 */
	@Deprecated
	public Long getId() {
		return id;
	}

	/**
	 * Retorna as configuracaoes do servidor
	 * @return {@link ServerConfiguration}
	 */
	public ServerConfiguration getConfig() {
		return this.config;
	}

	/**
	 * Configur ao servidor
	 */
	public void setConfig(ServerConfiguration config) {
		this.config = config;
	}
}
